package com.amqp.demo.statemachine.config;


import com.amqp.demo.statemachine.Events2;
import com.amqp.demo.statemachine.States2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;

import java.util.Map;

@Configuration
@EnableStateMachineFactory
//@EnableStateMachine
public class StateMachineConfig2 extends EnumStateMachineConfigurerAdapter<States2, Events2> {

    @Override
    public void configure(StateMachineConfigurationConfigurer<States2, Events2> config)
            throws Exception {
        config
                .withConfiguration()
                .autoStartup(true)
                .listener(listener());
    }

    @Override
    public void configure(StateMachineStateConfigurer<States2, Events2> States)
            throws Exception {
        States
                .withStates()
                .initial(States2.S0, fooAction())
                .state(States2.S0)
                .and()
                .withStates()
                .parent(States2.S0)
                .initial(States2.S1)
                .state(States2.S1)
                .and()
                .withStates()
                .parent(States2.S1)
                .initial(States2.S11)
                .state(States2.S11)
                .state(States2.S12)
                .and()
                .withStates()
                .parent(States2.S0)
                .state(States2.S2)
                .and()
                .withStates()
                .parent(States2.S2)
                .initial(States2.S21)
                .state(States2.S21)
                .and()
                .withStates()
                .parent(States2.S21)
                .initial(States2.S211)
                .state(States2.S211)
                .state(States2.S212);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<States2, Events2> transitions)
            throws Exception {
        transitions
                .withExternal()
                .source(States2.S1).target(States2.S1).event(Events2.A)
                .guard(foo1Guard())
                .and()
                .withExternal()
                .source(States2.S1).target(States2.S11).event(Events2.B)
                .and()
                .withExternal()
                .source(States2.S21).target(States2.S211).event(Events2.B)
                .and()
                .withExternal()
                .source(States2.S1).target(States2.S2).event(Events2.C)
                .and()
                .withExternal()
                .source(States2.S2).target(States2.S1).event(Events2.C)
                .and()
                .withExternal()
                .source(States2.S1).target(States2.S0).event(Events2.D)
                .and()
                .withExternal()
                .source(States2.S211).target(States2.S21).event(Events2.D)
                .and()
                .withExternal()
                .source(States2.S0).target(States2.S211).event(Events2.E)
                .and()
                .withExternal()
                .source(States2.S1).target(States2.S211).event(Events2.F)
                .and()
                .withExternal()
                .source(States2.S2).target(States2.S11).event(Events2.F)
                .and()
                .withExternal()
                .source(States2.S11).target(States2.S211).event(Events2.G)
                .and()
                .withExternal()
                .source(States2.S211).target(States2.S0).event(Events2.G)
                .and()
                .withInternal()
                .source(States2.S0).event(Events2.H)
                .guard(foo0Guard())
                .action(fooAction())
                .and()
                .withInternal()
                .source(States2.S2).event(Events2.H)
                .guard(foo1Guard())
                .action(fooAction())
                .and()
                .withInternal()
                .source(States2.S1).event(Events2.H)
                .and()
                .withExternal()
                .source(States2.S11).target(States2.S12).event(Events2.I)
                .and()
                .withExternal()
                .source(States2.S211).target(States2.S212).event(Events2.I)
                .and()
                .withExternal()
                .source(States2.S12).target(States2.S212).event(Events2.I);
    }

    @Bean
    public StateMachineListener<States2, Events2> listener() {
        return new StateMachineListenerAdapter<States2, Events2>() {
            @Override
            public void stateChanged(State<States2, Events2> from, State<States2, Events2> to) {
                System.out.println(Thread.currentThread().getName() + ":" + "State change to " + to.getId());
            }
        };
    }

    @Bean
    public FooGuard foo0Guard() {
        return new FooGuard(0);
    }

    @Bean
    public FooGuard foo1Guard() {
        return new FooGuard(1);
    }

    @Bean
    public FooAction fooAction() {
        return new FooAction();
    }

    private static class FooAction implements Action<States2, Events2> {

        @Override
        public void execute(StateContext<States2, Events2> context) {
            Map<Object, Object> variables = context.getExtendedState().getVariables();
            Integer foo = context.getExtendedState().get("foo", Integer.class);
            if (foo == null) {
                System.out.println("Init foo to 0");
                variables.put("foo", 0);
            } else if (foo == 0) {
                System.out.println("Switch foo to 1");
                variables.put("foo", 1);
            } else if (foo == 1) {
                System.out.println("Switch foo to 0");
                variables.put("foo", 0);
            }
        }
    }

    private static class FooGuard implements Guard<States2, Events2> {

        private final int match;

        public FooGuard(int match) {
            this.match = match;
        }

        @Override
        public boolean evaluate(StateContext<States2, Events2> context) {
            Object foo = context.getExtendedState().getVariables().get("foo");
            System.out.println("FooGuard:"+foo);
            return !(foo == null || !foo.equals(match));
        }
    }
}


